﻿using System;
using System.Collections.Generic;
using System.Linq;

using FirstFloor.Documents.IO;

namespace FirstFloor.Documents.Extensions
{
    /// <summary>
    /// Wraps the XpsClient event based asynchronous pattern in an IObservable.
    /// </summary>
    public static class XpsClientExtensions
    {
        /// <summary>
        /// Loads the XPS document using the default load settings.
        /// </summary>
        /// <param name="client">The client.</param>
        /// <param name="packageReader">The package reader.</param>
        /// <returns></returns>
        public static IObservable<XpsDocument> LoadXpsDocument(this XpsClient client, IPackageReader packageReader)
        {
            return LoadXpsDocument(client, packageReader, new LoadXpsDocumentSettings());
        }

        /// <summary>
        /// Loads the XPS document.
        /// </summary>
        /// <param name="client">The client.</param>
        /// <param name="packageReader">The package reader.</param>
        /// <param name="settings">The settings.</param>
        /// <returns></returns>
        public static IObservable<XpsDocument> LoadXpsDocument(this XpsClient client, IPackageReader packageReader, LoadXpsDocumentSettings settings)
        {
            if (client == null) {
                throw new ArgumentNullException("client");
            }

            return new AnonymousObservable<XpsDocument>(
                observer => {
                    var token = Guid.NewGuid();
                    var documentLoaded =
                        Observable.FromEvent<LoadXpsDocumentCompletedEventArgs>(client, "LoadXpsDocumentCompleted")
                            .Where(evt => ((Guid)evt.EventArgs.UserState) == token)
                            .Take(1);        //implicitly detaches event handler after event is received

                    bool errorOccurred = false;
                    // Subscribe to the IObservable. Under the hood Rx creates an anonymous observer class that invokes
                    // the three actions passed to the Subscribe method.
                    IDisposable unsubscribe =
                        documentLoaded.Subscribe(
                            // OnNext action
                            ev => {
                                // Propagate the exception if one is reported.  After this, all observers
                                // will continue to propagate this exception via the OnError method call
                                // until it is caught.
                                if (ev.EventArgs.Error != null) {
                                    errorOccurred = true;
                                    observer.OnError(ev.EventArgs.Error);
                                }
                                else if (!ev.EventArgs.Cancelled) {
                                    observer.OnNext(ev.EventArgs.Document);
                                }
                            },
                            // OnError action (propagate exception)
                            ex => observer.OnError(ex),
                            // OnCompleted action
                            () => {
                                // No need to call OnCompleted if there has been an exception.
                                // It is implicit that there will be no more data.
                                if (!errorOccurred) {
                                    observer.OnCompleted();
                                }
                            });

                    client.LoadXpsDocumentAsync(packageReader, settings, token);
                    return unsubscribe;
                });
        }

        /// <summary>
        /// Loads the specified fixed page using the default page load settings.
        /// </summary>
        /// <param name="client">The client.</param>
        /// <param name="pageContent">Content of the page.</param>
        /// <returns></returns>
        public static IObservable<FixedPage> LoadFixedPage(this XpsClient client, PageContent pageContent)
        {
            return LoadFixedPage(client, pageContent, new LoadFixedPageSettings());
        }

        /// <summary>
        /// Loads the specified fixed page
        /// </summary>
        /// <param name="client">The client.</param>
        /// <param name="pageContent">Content of the page.</param>
        /// <param name="settings">The settings.</param>
        /// <returns></returns>
        public static IObservable<FixedPage> LoadFixedPage(this XpsClient client, PageContent pageContent, LoadFixedPageSettings settings)
        {
            if (client == null) {
                throw new ArgumentNullException("client");
            }

            return new AnonymousObservable<FixedPage>(
                observer => {
                    var token = Guid.NewGuid();
                    var pageLoaded =
                        Observable.FromEvent<LoadFixedPageCompletedEventArgs>(client, "LoadFixedPageCompleted")
                            .Where(evt => ((Guid)evt.EventArgs.UserState) == token)
                            .Take(1);        //implicitly detaches event handler after event is received

                    bool errorOccurred = false;
                    // Subscribe to the IObservable. Under the hood Rx creates an anonymous observer class that invokes
                    // the three actions passed to the Subscribe method.
                    IDisposable unsubscribe =
                        pageLoaded.Subscribe(
                            // OnNext action
                            ev => {
                                // Propagate the exception if one is reported.  After this, all observers
                                // will continue to propagate this exception via the OnError method call
                                // until it is caught.
                                if (ev.EventArgs.Error != null) {
                                    errorOccurred = true;
                                    observer.OnError(ev.EventArgs.Error);
                                }
                                else if (!ev.EventArgs.Cancelled) {
                                    try {
                                        observer.OnNext(ev.EventArgs.LoadFixedPage(null));
                                    }
                                    catch (Exception error) {
                                        errorOccurred = true;
                                        observer.OnError(error);
                                    }
                                }
                            },
                            // OnError action (propagate exception)
                            ex => observer.OnError(ex),
                            // OnCompleted action
                            () => {
                                // No need to call OnCompleted if there has been an exception.
                                // It is implicit that there will be no more data.
                                if (!errorOccurred) {
                                    observer.OnCompleted();
                                }
                            });

                    client.LoadFixedPageAsync(pageContent, settings, token);
                    return unsubscribe;
                });
        }
    }
}
